# Дипломная работа Geekbrains
Создание веб-приложения на Laravel

Разработка web-приложения на языке программирования PHP с использованием
фреймворка Laravel.
У приложения будет реализована возможность принимать и отправлять данные через API,
так же будет добавлен веб-интерфейс для администрирования приложения
(добавление, удаление, редактирование данных).
***

## 1. Рабочее окружение

Для разворачивания рабочего окружения буду использовать контейнеры Docker и 
пакетный менеджер Docker-compose. Разворачиваю окружение на Windows 10, 
но так как Docker гарантирует, что приложение будет работать одинаково на 
разных устройствах и операционных системах, данный метод будет актуален вне 
зависимости от типа системы на которой нужно будет развернуть проект в дальнейшем.

Yaml файл:

```yaml
version: '3'
services:
  redis:
    container_name: redis
    hostname: redis
    restart: always
    depends_on:
      - mysql
    image: redis:7.2-alpine
    ports:
      - "6379:6379"

  nginx:
    container_name: nginx
    hostname: nginx
    restart: always
    depends_on:
      - redis
      - php-fpm
      - mysql
    build:
      context: ./docker/nginx
      dockerfile: Dockerfile
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./www/html:/var/www/html


  php-fpm:
    container_name: php-fpm
    hostname: php-fpm
    restart: always
    depends_on:
      - redis
      - mysql
    build:
      context: ./docker/php-fpm
      dockerfile: Dockerfile
    ports:
      - "9000:9000"
    volumes:
      - ./www/html:/var/www/html


  mysql:
    container_name: mysql
    hostname: mysql
    restart: always
    image: mysql:8.4.0
    ports:
      -  "3306:3306"
    volumes:
      - ./docker/mysql/data:/var/lib/mysql
      - ./docker/mysql/config:/etc/mysql/conf.d
      - ./docker/mysql/log:/var/log/mysql

    environment:
      MYSQL_ROOT_PASSWORD: 12345
      MYSQL_DATABASE: laravel
      MYSQL_USER: user
      MYSQL_PASSWORD: 12345
      TZ: Asia/Yekaterinburg
```

Dockerfile для PHP-FPM, так же в этот контейнер будет добавлен composer

Для выполнения команд Composer:
```
 docker exec -it php-fpm composer -V
```
```dockerfile
FROM php:8.3.9RC1-fpm-alpine3.20
RUN apk update
RUN curl -sSL https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions -o - | sh -s \
      gd xdebug pdo_mysql mbstring xml tokenizer session pcre openssl hash filter fileinfo dom curl ctype zip
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
RUN rm -rf /var/cache/apk/*
RUN addgroup -g 2000 node \
    && adduser -u 2000 -G node -s /bin/sh -D node
USER node
COPY php.ini /usr/local/etc/php/conf.d/php-custom.ini
EXPOSE  9000
WORKDIR /var/www/html
VOLUME /var/www/html
```

Dockerfile для nginx
```dockerfile
FROM nginx:1.27.0-alpine-slim
COPY mysite.local.conf /etc/nginx/conf.d/mysite.local.conf
WORKDIR /var/www/html
VOLUME /var/www/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
```

Конфигурация веб-сервера nginx, при необходимости добавить виртуальный хост, нужно добавить директиву server {}
```nginx configuration
server {
    listen 80;
    listen [::]:80;
    server_name mysite.local;
    root /var/www/html/mysite.local/public;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-Content-Type-Options "nosniff";

    index index.php;

    charset utf-8;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    error_page 404 /index.php;

    location ~ \.php$ {
        fastcgi_pass   php-fpm:9000;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_hide_header X-Powered-By;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}
```

Далее необходимо в настройках PHPStorm указать путь к php интерпретатору в Docker
![Изображение](./docker/img_for_git/img.png "Логотип Markdown")

## 2. Установка фреймворка Laravel

Для установки фреймворка запускаю Composer установленный в контейнер php-fpm:
```
 docker exec -it php-fpm composer create-project laravel/laravel .
```
Для удобства работы в IDE устанавливаю IDEHelper
```
docker exec -it php-fpm composer require --dev barryvdh/laravel-ide-helper 
```
Добавляю ServiceProvider
```
docker exec -it php-fpm php artisan vendor:publish --provider="Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider" --tag=config 
```

Генерация PHPDoc

```php artisan ide-helper:generate```

Генерация Models

```php artisan ide-helper:models```

Генерация метафайлов

```php artisan ide-helper:meta```


После добавление новых моделей или контроллеров необходимо заново генерировать PHPDoc и метафайлы, предварительно очистив кеш

```php artisan clear-compiled```
Можно объединить все команды в одну (для удобства)
```
docker exec -it php-fpm php artisan clear-compiled && php artisan ide-helper:generate && php artisan ide-helper:models &&  php artisan ide-helper:meta

```

### 2.1 Установка панели администрирования AdminLte
Для создания, удаления, редактирования данных веб-приложения использую административную панель AdminLTE. 
В конфигурацию Vite нужно прописать пути для компиляции JavaScript и CSS (SCSS), так же при необходимости прочие параметры, копирование файлов в из resource в public и т.д.
```typescript
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
    plugins: [
        laravel({
            input: [
                'resources/assets/admin/css/adminlte.css',
                'resources/assets/admin/plugins/fontawesome-free/css/all.css',
            ],
            refresh: true,
        }),

    ],
});
```

Запуск компиляции
```
npm run build
```

## 3. Миграции и отношения моделей приложения
Для текущего приложения необходимы следующие сущности:
1. Категория поста
2. Теги поста
3. Пост (статья)

Для каждой сущности нужно создать модель (Model) которая будет отвечать за работу с базой данных, контроллер (Controller) который будет отвечать за логику с работой с сущностью и миграцию (Migration) которая будет описывать структуру создаваемой таблицы относящейся к сущности.
Создать модель и миграцию можно одной командой:
```
php artisan make:model ModelName -m
```

### 3.1 Миграция для тегов и категорий идентична (за исключением имени таблицы)
```php
    public function up(): void
    {
        Schema::create('post_tag', function (Blueprint $table) {
            $table->id();
            $table->integer('post_id')->unsigned(); // связь между постами и тегами
            $table->integer('tag_id')->unsigned(); // связь между тегами и постами
            $table->timestamps();
        });
    }
```
### 3.2 Миграция для постов:
```php
    public function up(): void
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->increments('id'); //id
            $table->integer('category_id',false,true)->unsigned(); //связать категорию
            $table->string('name'); //название
            $table->string('slug'); //slug
            $table->string('description'); //описание
            $table->text('content'); //текст
            $table->string('image')->nullable(); //изображение
            $table->string('meta_keywords')->nullable();  //ключевые слова
            $table->string('meta_description')->nullable();   //описание
            $table->integer('views')->default(0); //количество просмотров
            $table->timestamps();
        });
    }
```
### 3.3 Миграция для связующей таблицы:
```php
    public function up(): void
    {
        Schema::create('tags', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->string('slug');
            $table->timestamps();
        });
    }
```

Выполняю миграцию командой:
```
php artisan migrate
```
Будут созданы описанные выше таблицы

---

Модель поста - отношение один ко многим
```php
class Category extends Model
{
    use HasFactory;
    public function posts(): HasMany // возвращает посты категории
    {
        return $this->hasMany(Post::class);
    }
}
```
Модель тега - отношение многие ко многим
```php
class Tag extends Model
{
    use HasFactory;
    public function posts(): BelongsToMany // возвращает посты относящиеся к тегу
    {
        return $this->belongsToMany(Post::class);
    }
}
```
Модель поста - две связи 
```php
class Post extends Model
{
    use HasFactory;

    public function tags(): BelongsToMany //возвращает теги поста
    {
        return $this->belongsToMany(Tag::class);
    }

    public function category(): BelongsTo //возвращает категорию поста
    {
        return $this->belongsTo(Category::class);
    }
}
```
---

Так как поля slug должно быть уникальным, использую библиотеку eloquent-sluggable, устанавливаю ее в проект с помощью Composer

```
php artisan vendor:publish --provider="Cviebrock\EloquentSluggable\ServiceProvider"
```

Необходимо обновить модели приложения, добавить трейт и метод в каждую из них и подключить пространство имен

```php
use Cviebrock\EloquentSluggable\Sluggable;
```
Добавляю метод для генерации уникального slug в каждую модель приложения
```php
    public function sluggable(): array
    {
        return [
            'slug' => [
                'source' => 'name'
            ]
        ];
    }
```
Для того чтобы выборка из базы данных происходила быстрее, добавляю в файлы миграции уникальный ключ к полю slug
```php
$table->string('slug')->unique();
```
И обновляю миграцию командой:
```
php artisan migrate:refresh
```

## 4. Управление категориями приложения
Для реализации функционала управления категориями, тегами и постами буду использовать Resource контроллеры.
В Resource контроллере есть следующие методы:
 - index() — отображение списка ресурса;
 - create() — показ формы для создания нового ресурса;
 - store(Request $request) — хранение вновь созданного ресурса в хранилище;
 - show($id) — отображение указанного ресурса;
 - edit($id) — показ формы для редактирования указанного ресурса;
 - update(Request $request, $id) — обновление указанного ресурса в хранилище;
 - destroy($id) — удаление указанного ресурса из хранилища.
 - 
   Для генерации Resource контроллера в Laravel используется команда:
```
make:controller с опцией --resource
```

Добавлена пагинация результатов выдачи
```
php artisan vendor:publish --tag=laravel-pagination
```
Эта команда поместит представления в resources/views/vendor/pagination в каталог
 приложения. Файл tailwind.blade.php в каталоге соответствует представлению нумерации страниц по умолчанию.

Валидацию вводимых данных необходимо исключить из логики контроллера, поэтому создаю RequestController в который перенесу валидационные правила.
```
php artisan make:request Category\StoreCategoryRequest 
```

```php
    public function store(StoreCategoryRequest $request): \Illuminate\Http\RedirectResponse
    {
        Category::create($request->all());
        return redirect()->route('categories.index')->with('message',  'Category created successfully');
    }
```
## 4. Управление тегами приложения
[Ссылка на коммит](https://gitverse.ru/nebubu/gb_diplom/commit/9452f5f71d09fc51b8cb42d28bc3d6fe58ab0d64 "Ссылка на коммит")
Реализация идентична с реализацией для категорий

## 4. Управление постами приложения
